package scuba.DiveBuddy.Core;

import org.joda.time.DateTime;
import org.joda.time.Hours;
import org.joda.time.Interval;
import org.joda.time.Minutes;
import scuba.DiveBuddy.Class.LogEntry;
import scuba.DiveBuddy.Class.RAT;
import scuba.DiveBuddy.Enums.PressureGroupEnum;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Alex Anderson
 * @version 1.0
 * @since {15/03/14}
 */
public class RDPCalculator {

    public static final int[] Depths = { 10, 12, 14, 16, 18, 20, 22, 25, 30, 35, 40, 42 };

    //<editor-fold desc="----> DepthToPGTables">
    public static final int[][] DepthToPGTables =
            {
                    { 10, 20, 26, 30, 34, 37, 41, 45, 50, 54, 59, 64, 70, 75, 82, 88, 95, 104, 112, 122, 133, 145, 160, 178, 199, 219 }, // DEPTH  = 10
                    { 9, 17, 23, 26, 29, 32, 35, 38, 42, 45, 49, 53, 57, 62, 66, 71, 76, 82, 88, 94, 101, 108, 116, 125, 134, 147 },// DEPTH  = 12
                    { 8, 15, 19, 22, 24, 27, 29, 32, 35, 37, 40, 43, 47, 50, 53, 57, 61, 64, 68, 73, 77, 82, 87, 89, 98 },// DEPTH  = 14
                    { 7, 13, 17, 19, 21, 23, 25, 27, 29, 32, 34, 37, 39, 42, 45, 48, 50, 53, 56, 60, 63, 67, 70, 72 },// DEPTH  = 16
                    { 6, 11, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 39, 41, 43, 46, 48, 51, 53, 55, 56 },// DEPTH  = 18
                    { 6, 10, 13, 15, 16, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 45 },// DEPTH  = 20
                    { 5, 9, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 29, 30, 32, 34, 36, 37 },// DEPTH  = 22
                    { 4, 8, 10, 11, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29 },// DEPTH  = 25
                    { 3, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20 },// DEPTH  = 30
                    { 3, 5, 7, 8, -1, 9, 10, 11, 12, 13, 14 },// DEPTH  = 35
                    { -1, 5, 6, -1, 7, 8, 9 },// DEPTH  = 40
                    { -1, 4, -1, 6, 7, 8 },// DEPTH  = 42
            };
    //</editor-fold>

    public static Map<PressureGroupEnum, int[]> SurfaceIntervalPGTable;
    public static Map<PressureGroupEnum, RAT[]> RepetitiveDiveRAT;

    //Static Construct
    static {
        SurfaceIntervalPGTable = new HashMap<PressureGroupEnum, int[]>();

        SurfaceIntervalPGTable.put(PressureGroupEnum.A, new int[] { 180});
        SurfaceIntervalPGTable.put(PressureGroupEnum.B, new int[] { 47, 228});
        SurfaceIntervalPGTable.put(PressureGroupEnum.C, new int[] { 21, 69, 250});
        SurfaceIntervalPGTable.put(PressureGroupEnum.D, new int[] { 8, 30, 78, 259});
        SurfaceIntervalPGTable.put(PressureGroupEnum.E, new int[] { 7, 16, 38, 87, 268});
        SurfaceIntervalPGTable.put(PressureGroupEnum.F, new int[] { 7, 15, 24, 46, 94, 275});
        SurfaceIntervalPGTable.put(PressureGroupEnum.G, new int[] { 6, 13, 22, 31, 53, 101, 282});
        SurfaceIntervalPGTable.put(PressureGroupEnum.H, new int[] { 5, 12, 20, 28, 37, 59, 107, 288});
        SurfaceIntervalPGTable.put(PressureGroupEnum.I, new int[] { 5, 11, 18, 26, 34, 43, 65, 113, 294});
        SurfaceIntervalPGTable.put(PressureGroupEnum.J, new int[] { 5, 11, 17, 24, 31, 40, 49, 71, 119, 300});
        SurfaceIntervalPGTable.put(PressureGroupEnum.K, new int[] { 4, 10, 16, 22, 29, 37, 45, 54, 76, 124, 305});
        SurfaceIntervalPGTable.put(PressureGroupEnum.L, new int[] { 4, 9, 15, 21, 27, 34, 42, 50, 59, 81, 129, 310});
        SurfaceIntervalPGTable.put(PressureGroupEnum.M, new int[] { 4, 9, 14, 19, 25, 32, 39, 46, 55, 64, 85, 134, 315});
        SurfaceIntervalPGTable.put(PressureGroupEnum.N, new int[] { 3, 8, 13, 18, 24, 30, 36, 43, 51,59, 68, 90, 138, 319});
        SurfaceIntervalPGTable.put(PressureGroupEnum.O, new int[] { 3, 8, 12, 17, 21, 28, 34, 41, 47, 55, 63, 72, 94, 143, 324});
        SurfaceIntervalPGTable.put(PressureGroupEnum.P, new int[] { 3, 7, 12, 16, 21, 27, 32, 38, 45, 51, 59, 67, 76, 98, 147, 328});
        SurfaceIntervalPGTable.put(PressureGroupEnum.Q, new int[] { 3, 7, 11, 16, 20, 25, 30, 36, 42, 48, 55, 63, 71, 80, 102, 150, 331});
        SurfaceIntervalPGTable.put(PressureGroupEnum.R, new int[] { 3, 7, 11, 15, 19, 24, 29, 34, 40, 46, 52, 59, 67, 75, 84, 106, 154, 335});
        SurfaceIntervalPGTable.put(PressureGroupEnum.S, new int[] { 3, 6, 10, 14, 18, 23, 27, 32, 37, 43, 49, 56, 63, 70, 78, 87, 109, 158, 339});
        SurfaceIntervalPGTable.put(PressureGroupEnum.T, new int[] { 2, 6, 10, 13, 19, 22, 26, 31, 36, 41, 49, 53, 59, 66, 73, 82, 91, 113, 161, 342});
        SurfaceIntervalPGTable.put(PressureGroupEnum.U, new int[] { 2, 6, 9, 13, 17, 21, 25, 29, 34, 39, 44, 50, 56, 62, 69, 77, 85, 94, 116, 164, 345});
        SurfaceIntervalPGTable.put(PressureGroupEnum.V, new int[] { 2, 5, 9, 12, 16, 20, 24, 28, 33, 37, 42, 47, 53, 59, 65, 72, 80, 88, 97, 119, 167, 348});
        SurfaceIntervalPGTable.put(PressureGroupEnum.W, new int[] { 2, 5, 8, 12, 15, 19, 23, 27, 31, 36, 40, 45, 50, 56, 62, 68, 75, 83, 91, 100, 122, 170, 351});
        SurfaceIntervalPGTable.put(PressureGroupEnum.X, new int[] { 2, 5, 8, 11, 15, 18, 22, 26, 30, 34, 39, 43, 48, 53, 59, 65, 71, 78, 86, 94, 103, 125, 173, 354});
        SurfaceIntervalPGTable.put(PressureGroupEnum.Y, new int[] { 2, 5, 8, 11, 14, 18, 21, 25, 29, 33, 37, 41, 46, 51, 56, 62, 68, 74, 81, 89, 97, 106, 128, 176, 357});
        SurfaceIntervalPGTable.put(PressureGroupEnum.Z, new int[] { 2, 5, 8, 11, 14, 17, 20, 24, 28, 31, 35, 40, 44, 49, 54, 59, 65, 71, 77, 84, 91, 100, 109, 131, 179, 360});

        RepetitiveDiveRAT = new HashMap<PressureGroupEnum, RAT[]>();

        RepetitiveDiveRAT.put(PressureGroupEnum.A, new RAT[] {
                new RAT(10, 209), new RAT(9, 138), new RAT(8, 90), new RAT(7, 65), new RAT(6, 50), new RAT(6, 39),
                new RAT(5, 32), new RAT(4, 25), new RAT(3, 17), new RAT(3, 11), new RAT(2, 7)});

        RepetitiveDiveRAT.put(PressureGroupEnum.B, new RAT[] {
                new RAT(20, 199), new RAT(17, 130), new RAT(15, 83), new RAT(13, 59), new RAT(11, 45), new RAT(10, 35),
                new RAT(9, 28), new RAT(8, 21), new RAT(6, 14), new RAT(5, 9), new RAT(5, 4) });

        RepetitiveDiveRAT.put(PressureGroupEnum.C, new RAT[] {
                new RAT(26, 193), new RAT(23, 124), new RAT(19, 79), new RAT(17, 55), new RAT(15, 41), new RAT(13, 32),
                new RAT(12, 25), new RAT(10, 19), new RAT(8, 12), new RAT(7, 7), new RAT(6, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.D, new RAT[] {
                new RAT(30, 189), new RAT(23, 121), new RAT(22, 76), new RAT(19, 53), new RAT(16, 40), new RAT(15, 30),
                new RAT(13, 24), new RAT(11, 18), new RAT(9, 11), new RAT(8, 6), new RAT(7, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.E, new RAT[] {
                new RAT(34, 185), new RAT(29, 118), new RAT(24, 74), new RAT(21, 51), new RAT(18, 38), new RAT(16, 29),
                new RAT(15, 22), new RAT(13, 16), new RAT(10, 10), new RAT(9, 5), new RAT(7, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.F, new RAT[] {
                new RAT(37, 182), new RAT(32, 115), new RAT(27, 71), new RAT(23, 49), new RAT(20, 36), new RAT(18, 27),
                new RAT(16, 21), new RAT(14, 15), new RAT(11, 9), new RAT(9, 5), new RAT(8, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.G, new RAT[] {
                new RAT(41, 178), new RAT(35, 112), new RAT(29, 69), new RAT(25, 47), new RAT(22, 34), new RAT(20, 25),
                new RAT(18, 19), new RAT(15, 14), new RAT(12, 8), new RAT(10, 4), new RAT(9, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.H, new RAT[] {
                new RAT(45, 174), new RAT(38, 109), new RAT(32, 66), new RAT(27, 45), new RAT(24, 32), new RAT(21, 24),
                new RAT(19, 18), new RAT(17, 12), new RAT(13, 7), new RAT(11, 3) });

        RepetitiveDiveRAT.put(PressureGroupEnum.I, new RAT[] {
                new RAT(50, 169), new RAT(42, 105), new RAT(35, 63), new RAT(29, 43), new RAT(26, 30), new RAT(23, 22),
                new RAT(21, 16), new RAT(18, 11), new RAT(14, 6), new RAT(12, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.J, new RAT[] {
                new RAT(54, 165), new RAT(45, 102), new RAT(37, 61), new RAT(32, 40), new RAT(28, 28), new RAT(25, 20),
                new RAT(22, 15), new RAT(19, 10), new RAT(15, 5), new RAT(13, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.K, new RAT[] {
                new RAT(59, 160), new RAT(49, 98), new RAT(40, 58), new RAT(34, 38), new RAT(30, 26), new RAT(26, 19),
                new RAT(24, 13), new RAT(21, 8), new RAT(16, 4), new RAT(14, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.L, new RAT[] {
                new RAT(64, 155), new RAT(53, 94), new RAT(43, 55), new RAT(37, 35), new RAT(32, 24), new RAT(28, 17),
                new RAT(25, 12), new RAT(22, 7), new RAT(17, 3)});

        RepetitiveDiveRAT.put(PressureGroupEnum.M, new RAT[] {
                new RAT(70, 149), new RAT(57, 90), new RAT(47, 51), new RAT(39, 33), new RAT(34, 22), new RAT(30, 15),
                new RAT(27, 10), new RAT(23, 6), new RAT(19, -1)});

        RepetitiveDiveRAT.put(PressureGroupEnum.N, new RAT[] {
                new RAT(75, 144), new RAT(62, 85), new RAT(50, 48), new RAT(42, 30), new RAT(36, 20), new RAT(32, 13),
                new RAT(29, 8), new RAT(25, 4), new RAT(20, -1)});

        RepetitiveDiveRAT.put(PressureGroupEnum.O, new RAT[] {
                new RAT(82, 137), new RAT(66, 81), new RAT(53, 45), new RAT(45, 27), new RAT(39, 17), new RAT(34, 11),
                new RAT(30, 7), new RAT(26, 3) });

        RepetitiveDiveRAT.put(PressureGroupEnum.P, new RAT[] {
                new RAT(88, 131), new RAT(71, 76), new RAT(57, 41), new RAT(48, 24), new RAT(41, 15), new RAT(36, 9),
                new RAT(32, 5), new RAT(28, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.Q, new RAT[] {
                new RAT(95, 124), new RAT(76, 71), new RAT(61, 37), new RAT(50, 22), new RAT(43, 13), new RAT(38, 7),
                new RAT(34, 3), new RAT(29, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.R, new RAT[] {
                new RAT(104, 115), new RAT(82, 65), new RAT(64, 34), new RAT(53, 19), new RAT(46, 10), new RAT(40, 5),
                new RAT(36, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.S, new RAT[] {
                new RAT(112, 107), new RAT(88, 59), new RAT(68, 30), new RAT(56, 16), new RAT(48, 8), new RAT(42, 3),
                new RAT(37, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.T, new RAT[] {
                new RAT(122, 97), new RAT(94, 53), new RAT(73, 25), new RAT(60, 12), new RAT(51, 5), new RAT(44, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.U, new RAT[] {
                new RAT(133, 86), new RAT(101, 46), new RAT(77, 21), new RAT(63, 9), new RAT(53, 3), new RAT(45, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.V, new RAT[] {
                new RAT(145, 74), new RAT(108, 39), new RAT(82, 16), new RAT(67, 5), new RAT(55, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.W, new RAT[]{
                new RAT(160, 59), new RAT(116, 31), new RAT(87, 11), new RAT(70, 2), new RAT(56, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.X, new RAT[] {
                new RAT(178, 41), new RAT(125, 22), new RAT(92, 6), new RAT(72, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.Y, new RAT[] {
                new RAT(199, 20), new RAT(134, 13), new RAT(98, -1) });

        RepetitiveDiveRAT.put(PressureGroupEnum.Z, new RAT[] {
                new RAT(219, -1), new RAT(147, -1) });
    }


    private static PressureGroupEnum getFirstPressureGroup(float maxDepth, int timeInMins)
            throws Exception {

        if(maxDepth > 42f)
            throw new Exception("Depth must be less than 42m");

        //1 Camp the depth UP to the nearest entry in the Depth Table
        int clampedDepthRange = 0;
        for(int  i = 0; i < Depths.length; i++) {
            if(maxDepth <= Depths[i]){
                clampedDepthRange = i;
                break;
            }
        }

        //2 Follow the appropriate column to achieve a first PressureGroup
        for(int i = 0; i < DepthToPGTables[clampedDepthRange].length; i++) {
            int pgTableTime = DepthToPGTables[clampedDepthRange][i];

            if(timeInMins <= DepthToPGTables[clampedDepthRange][i])
                return PressureGroupEnum.values()[i]; //1-24 index of pressureGroups

        }

        throw new Exception("Error undefined pressure gradient");
    }

    private static PressureGroupEnum getPressureGroupAfterSurfaceInterval(PressureGroupEnum pg, int surfaceIntervalInMins)
            throws Exception {

        //Take the Pressure group from Stage 1 and find the interval which contains our surface interval.
        int[] intervals = SurfaceIntervalPGTable.get(pg);

        for(int i =0 ; i < intervals.length; i++) {
            int intervalMax = intervals[i];

            if(surfaceIntervalInMins < intervalMax) {
                //we have our new Pressure group
                return PressureGroupEnum.values()[intervals.length - (i + 1)];
            }
        }

        throw new Exception("Unrecognised Surface interval");
    }

    private static RAT getRATForRepetitiveDive(PressureGroupEnum pgAfterSI, float intendedDepth)
            throws Exception {

        if(intendedDepth > 42f)
            throw new Exception("Depth must be less than 42m");

        //1 Camp the depth UP to the nearest entry in the Depth Table
        int clampedDepthRange = 0;
        for(int  i = 0; i < Depths.length; i++) {
            if(intendedDepth <= Depths[i]){
                clampedDepthRange = i;
                break;
            }
        }

        RAT[] rats = RepetitiveDiveRAT.get(pgAfterSI);

        try {
            return rats[clampedDepthRange];
        }
        catch(NullPointerException np) {
            //There is not RAT
            throw new Exception("");
        }
    }



    public static PressureGroupEnum AssignPressureGroupForDive(LogEntry entry)
            throws Exception {

        PressureGroupEnum group = getFirstPressureGroup(entry.getMaxDepth(), entry.getDiveDuration());
        entry.setPressureGroup(group);
        return group;
    }

    public static PressureGroupEnum AssignPressureGroupForDive(LogEntry previousDive, LogEntry dive)
            throws Exception {

        //calculate the interval between dives
        DateTime lastDiveOut = previousDive.getDiveDate().toDateTime(previousDive.getTimeOut());
        DateTime thisDiveIn = dive.getDiveDate().toDateTime(dive.getTimeIn());
        int surfaceInterval = Minutes.minutesBetween(lastDiveOut, thisDiveIn).getMinutes();

        //if the surface interval is over 6hrs - ignore it and return as normal
        if(surfaceInterval > Minutes.minutes(6 * 60).getMinutes())
            return AssignPressureGroupForDive(dive);


        //else
        PressureGroupEnum pressureAfterSI = getPressureGroupAfterSurfaceInterval(previousDive.getPressureGroup(), surfaceInterval);
        RAT ratAfterLastDive = getRATForRepetitiveDive(pressureAfterSI, dive.getMaxDepth());
        PressureGroupEnum pg = getFirstPressureGroup(dive.getMaxDepth(), dive.getDiveDuration() + ratAfterLastDive.RNT);
        dive.setPressureGroup(pg);
        return pg;
    }

}
